function Page()
{
  this.objname = "Page";
  this.objects = [];
  this.undo_objects = [];
}


Page.prototype.pushObj = function(obj) {
  this.objects.push(obj);
}


Page.prototype.popObj = function() {
  return this.objects.pop();
}


Page.prototype.undo = function() {
  var obj = this.objects.pop();
  if(obj) {
    this.undo_objects.push(obj);
  }
}


Page.prototype.redo = function() {
  var obj = this.undo_objects.pop();
  if(obj) {
    this.objects.push(obj);
  }
}


Page.prototype.redraw = function() {
  var i = 0;
  var j = 1;
  var len = this.objects.length;

  writeDebug("redraw: " + this.objects.length, 6);

  cctx.clearRect(0, 0, settings.width, settings.height);

  for(i=0; i<len; i++) {
    this.objects[i].drawObj();
  }
}


Page.prototype.reset = function() {
  writeDebug("reset: " + this.objects.length, 6);
  this.objects = [];
}


Page.prototype.restore = function(objs) {
  var len = objs.length;
  writeDebug("restore: " + objs.length, 6);

  this.reset();

  for(i=0; i<len; i++) {
    if(objs[i].objname === "Path") {
      p = new Path();
      p.path = objs[i].path;
      p.strokeStyle = objs[i].strokeStyle;
      p.lineWidth = objs[i].lineWidth;
      this.pushObj(p);
    }
    else if(objs[i].objname === "Text") {
      var t = new Text();
      t.x = objs[i].x;
      t.y = objs[i].y;
      t.fillStyle = objs[i].strokeStyle;
      t.font = objs[i].font;
      t.text = objs[i].text;
      this.pushObj(p);
    }
  }
}


function Text() 
{
  this.id = 0;
  this.objname = "Text";
  this.textBaseline = "middle";
  this.fillStyle = "";
  this.font = "20px sans-serif";
  this.text = "";
  this.x = 0;
  this.y = 0;
}

Text.prototype.drawObj = function() {
  cctx.textBaseline = this.textBaseline;
  cctx.fillStyle = this.strokeStyle;
  cctx.font = this.font;
  cctx.fillText(this.text, this.x, this.y);
}


function Path() 
{
  this.id = 0;
  this.objname = "Path";
  this.path = [];
  this.strokeStyle = "";
  this.lineWidth = 0;
}


Path.prototype.drawObj = function() {
  var i = 1;
  cctx.strokeStyle = this.strokeStyle;
  cctx.lineWidth = this.lineWidth;
  cctx.beginPath();
  cctx.moveTo(this.path[0].X, this.path[0].Y);
  for(i = 1; i < this.path.length; i++) {
    if(this.erase === 1) {
      cctx.clearRect(this.path[i].X, this.path[i].Y, settings.eraseWidth, settings.eraseWidth);
      
    }
    else {
      cctx.lineTo(this.path[i].X, this.path[i].Y);
      cctx.stroke();
    }
  }
  cctx.closePath();
}

//bounding box object.
function BBox() {

  this.id = 0;
  this.next = 0;

  this.path = [];

  this.strokeStyle = "";
  this.lineWidth = 0;

  //basic definition for bbox spacing
  this.hdrsz = 30;
  this.buffer = 8;
  this.bordersz = 2;
  this.anchorsz = 8;

  //position related values
  this.xmin = 0;
  this.ymin = 0;
  this.worig = 0;
  this.horig = 0;
  this.x = 0;
  this.y = 0;
  this.w = 0;
  this.h = 0;
  this.endx = 0;
  this.offx = 0;
  this.startx = 0;
  this.endy = 0;
  this.offy = 0;
  this.starty = 0;

  //movement related flags
  this.selflag = 0; //indicates bbox is selected
  this.moveflag = 0; //enable move of bbox
  this.resizeflag = 0; //enable resize of box
  this.highlightflag = 0; //for managing visibility of bbox
  this.moved = 0;
  this.removed = 0;
  this.inuse = 0;
  this.resized = 0;
  this.resizeDir = "";

  this.writeDebug("created new bounding box id: " + this.id, 6);

}


BBox.prototype = {

  reset: function() {
    this.path = [];
    this.xmin = 0;
    this.ymin = 0;
    this.worig = 0;
    this.horig = 0;
    this.x = 0;
    this.y = 0;
    this.w = 0;
    this.h = 0;
    this.endx = 0;
    this.offx = 0;
    this.startx = 0;
    this.endy = 0;
    this.offy = 0;
    this.starty = 0;

    this.selflag = 0;
    this.moveflag = 0;
    this.resizeflag = 0;
    this.highlightflag = 0;
    this.moved = 0;
    this.removed = 0;
    this.inuse = 0;
    this.resized = 0;
    this.resizeDir = "";
  },

  create: function(x, y, w, h) {
    this.xmin = x;
    this.ymin = y;
    this.worig = w;
    this.horig = h;
    //add space for gui elements
    this.x = x - this.buffer - this.anchorsz;
    this.y = y - this.buffer - this.hdrsz - this.anchorsz;
    this.w = w + (2*this.buffer) + (2*this.bordersz) + this.anchorsz;
    this.h = h + (2*this.buffer) + (2*this.bordersz) + this.anchorsz + this.hdrsz;

    this.writeDebug("create: this.x=" + this.x + " this.y=" + this.y + " this.w=" + this.w + " this.h=" + this.h, 3);

    //draw something for debugging only. Ideally a bbox will
    //show up only on pen events
    this.highlight();
  },


  redraw: function() {
    this.writeDebug("redraw: selected=" + this.selflag + " high=" + this.highlightflag, 6);
    if(this.selflag === 1) {
      this.highlightflag = 0;
      this.highlight();
    }
    cctx.strokeStyle = this.strokeStyle;
    cctx.lineWidth = this.lineWidth;
    this.drawPath();
  },


  isBBoxSelected: function(x, y) {
    this.writeDebug("isBBoxSelected: x=" + x + " y=" + y + " this.x=" + this.x + " this.y=" + this.y, 5);
    if( x >= this.x && x <= this.x + this.w &&
        y >= this.y && y <= this.y + this.h ) {
      this.writeDebug("selected", 5);
      return 1;
    }
    return 0;
  },


  isPathSelected: function(x, y) {
    this.writeDebug("isPathSelected: x=" + x + " y=" + y + " this.x=" + this.x + " this.y=" + this.y, 5);
    if( x >= this.xmin && x <= this.xmin + this.worig &&
        y >= this.ymin && y <= this.ymin + this.horig ) {
      this.writeDebug("selected", 5);
      return 1;
    }
    return 0;
  },


  highlight: function() {
    this.writeDebug("highlight: highlighted=" + this.highlightflag, 6);
    cctx.save();

    //debug area covers all gui elements, borders, anchor points etc.
    /*
    cctx.globalAlpha = 0.7;
    cctx.fillStyle = "#f00";
    cctx.fillRect(this.x, this.y, this.w, this.h);
    */

    cctx.globalAlpha = 0.1;
    cctx.fillStyle = "#00f";
    cctx.fillRect(this.x+this.anchorsz/2, this.y+this.anchorsz/2, 
                  this.w-this.anchorsz, this.h-this.anchorsz);

    //draw a header area for remove and rotate buttons
    cctx.globalAlpha = 1.0;
    cctx.fillStyle = "#87ceeb";
    cctx.fillRect(this.x+this.anchorsz/2, this.y+this.anchorsz/2, this.w-this.anchorsz, this.hdrsz);

    //draw a remove button
    cctx.lineWidth = 1;
    cctx.strokeStyle="#0000ff";
    cctx.font="15px sans-serif";
    cctx.strokeText("X", this.x+this.w-20, this.y+this.hdrsz-this.buffer);

    //draw a border around the highlighted box
    cctx.lineWidth = this.bordersz;
    cctx.strokeStyle = "#87ceeb";
    cctx.strokeRect(this.x+this.anchorsz/2, this.y+this.anchorsz/2,
                  this.w-this.anchorsz, this.h-this.anchorsz);

    //create the anchor points
    cctx.fillStyle = "#00f";
    //NW
    //cctx.fillRect(this.x, this.y, this.anchorsz, this.anchorsz);
    //N
    cctx.fillRect(this.x+(this.w/2), this.y, this.anchorsz, this.anchorsz);
    //NE
    //cctx.fillRect(this.x+this.w-this.anchorsz, this.y, this.anchorsz, this.anchorsz);
    //W
    cctx.fillRect(this.x, this.y+(this.h/2), this.anchorsz, this.anchorsz);
    //SW
    //cctx.fillRect(this.x, this.y+this.h-this.anchorsz, this.anchorsz, this.anchorsz);
    //S
    cctx.fillRect(this.x+(this.w/2), this.y+this.h-this.anchorsz, this.anchorsz, this.anchorsz);
    //SE
    //cctx.fillRect(this.x+this.w-this.anchorsz, this.y+this.h-this.anchorsz, this.anchorsz, this.anchorsz);
    //E
    cctx.fillRect(this.x+this.w-this.anchorsz, this.y+(this.h/2), this.anchorsz, this.anchorsz);

    cctx.restore();
    this.highlightflag = 1;
  },


  unhighlight: function() {
    this.writeDebug("unhighlight: highlighted=" + this.highlightflag, 6);
    cctx.save();
    cctx.globalAlpha = 0.0;
    cctx.fillStyle = "#00f";
    cctx.clearRect(this.x-2, this.y-2, this.w+6, this.h+6);
    cctx.fillRect(this.x-2, this.y-2, this.w+6, this.h+6);
    cctx.restore();
    this.drawPath();
    this.highlightflag = 0;
    this.selflag = 0;
  },


  updateOffset: function(pt) {
    this.offx = pt.X - this.startx;
    this.offy = pt.Y - this.starty;
    //offset could be negative
    this.writeDebug("updateOffset: offx=" + this.offx + " offy=" + this.offy, 6);
  },


  updatePath: function() {
    var i = 0;
    var len = this.path.length;
    for(i=0; i < len; i++) {
      this.path[i].X = this.path[i].X + this.offx;
      this.path[i].Y = this.path[i].Y + this.offy;
    }
  },

  scalePath: function(sx, sy, offx, offy) {
    var i = 0;
    var len = this.path.length;
    var dx = this.xmin*(sx-1);
    var dy = this.ymin*(sy-1);
    for(i=0; i < len; i++) {
      if(offx < 0 || offy < 0) {
        this.path[i].X = (this.path[i].X / sx) + dx;
        this.path[i].Y = (this.path[i].Y / sy) + dy;
      }
      else {
        this.path[i].X = (this.path[i].X * sx) - dx;
        this.path[i].Y = (this.path[i].Y * sy) - dy;
      }
      this.writeDebug("scalePath: x=" + this.path[i].X + " sx=" + sx, 6);
    }
  },


  drawPath: function() {
    var i = 1;
    cctx.strokeStyle = this.strokeStyle;
    cctx.lineWidth = this.lineWidth;
    cctx.beginPath();
    cctx.moveTo(this.path[0].X, this.path[0].Y);
    for(i=1; i < this.path.length; i++) {
      cctx.lineTo(this.path[i].X, this.path[i].Y);
      cctx.stroke();
    }
    cctx.closePath();
  },


  remove: function() {
    free_bbox(this.id);
    this.writeDebug("remove: id " + this.id + " len: " + boxes.length, 3);
    redrawCanvas();
    var data = {
      'type': 'bbox',
      'flag': 'remove',
      'groupname': groupname,
      'username': username,
      'gid': gid,
      'uid': uid,
      'id': this.id,
      'box': this
    }
    if(wsock) {
      wsock.send(JSON.stringify(data));
    }
  },

  //events on the bbox are handled here
  
  pendown: function(pt) {
    // here means the mouse was down with the bbox boundaries
    this.writeDebug("pendown: selected=" + this.selflag, 5);
    //if this is box is previously selected unhighlight it
    if(this.selflag === 0) {
      this.highlight();
      this.selflag = 1;
    }
    
    //if clicked on remove button remove the bbox
    if(this.chkRemoveClick(pt)) {
      this.remove();
      //reset the global selected flag since the global pen events will call the specific box pen events which is not good
      //if the box is not in the list
      selected = -1;
    }

    var anch = this.chkAnchorClick(pt);
    //if clicked on anchor button set the resize flag otherwise
    //set the move flag.
    if(anch !== "") {
      this.writeDebug("pendown: anchor " + anch, 9);
      this.resizeflag = 1;
      this.resizeDir = anch;
    }
    else {
      //enable move
      this.writeDebug("pendown: changing move to 1 from " + this.moveflag, 5);
      this.moveflag = 1;
    }

    //since the mouse is down here, make is the start
    //of a potential movement
    this.startx = pt.X;
    this.starty = pt.Y;

    //hasnt moved or resized yet
    this.moved = 0;
    this.resized = 0;
  },

  penup: function(pt) {
    this.writeDebug("penup: selected=" + this.selflag + " moved=" + this.moved, 5);
    //if the box has been moved, on penup un-highlight
    //the box, since thats a new placement and the move
    //activity is over. Otherwise keep the box highlighted
    //to indicate the user that something needs to be done.
    if(this.moved === 1) {
      this.moved = 0;
      this.writeDebug("the path len is: " + this.path.length, 7);
      this.endx = pt.X;
      this.endy = pt.Y;
      this.unhighlight();
      redrawCanvas();

      //send the box co-ord again if moved
      var data = {
        'type': 'bbox',
        'flag': 'move',
        'groupname': groupname,
        'username': username,
        'gid': gid,
        'uid': uid,
        'id': this.id,
        'box': this
      }
      if(wsock) {
        wsock.send(JSON.stringify(data));
      }
    }
    this.writeDebug("penup: changing move to 0 from " + this.moveflag, 5);
    this.moveflag = 0;
    this.resizeflag = 0;
    this.resizeDir = "";
  },

  penmove: function(pt) {
    if (this.selflag === 1) {
      this.writeDebug("penmove: move selflag=" + this.selflag + " moveflag=" + this.moveflag + " resizeflag=" + this.resizeflag, 7);

      //get the offset from the start position, where the
      //mouse was downed, updates offx and offy
      //this is difference between the bbox x,y and mousedown x,y
      this.updateOffset(pt);

      //update the start position since the next time penmove is called we need to get the offset from this current
      //position
      this.startx = pt.X;
      this.starty = pt.Y;
      if(this.moveflag === 1) {
        this.x = this.x + this.offx;
        this.y = this.y + this.offy;
        this.xmin = this.xmin + this.offx;
        this.ymin = this.ymin + this.offy;
        //update the path in the bbox with the new offset
        this.updatePath();
        //indicating whether the bbox actually moved or not
        this.moved = 1;

      }
      else if(this.resizeflag === 1) {
        var sx = 1.025;
        var sy = 1.025;
        if(this.resizeDir === "N") {
          /*XXX
          this.y = this.y + this.offy;
          this.h = this.h - this.offy;
          */
        }
        else if(this.resizeDir === "S") {
          if(this.offy < 0) {
            this.h = this.h/sy;
            this.scalePath(1, sy, this.offx, this.offy);
          }
          else {
            this.h = this.h*sy;
            this.scalePath(1, sy, this.offx, this.offy);
          }
        }
        else if(this.resizeDir === "E") {
          if(this.offx < 0) {
            this.w = this.w/sx;
            this.scalePath(sx, 1, this.offx, this.offy);
          }
          else {
            this.w = this.w*sx;
            this.scalePath(sx, 1, this.offx, this.offy);
          }
        }
        else if (this.resizeDir === "W") {
          /*XXX
          this.x = this.x + this.offx;
          this.w = this.w - this.offx;
          */
        }
        this.worig = this.w;
        this.horig = this.h;
        this.resized = 1;

        //send the box co-ord again if resized
        var data = {
          'type': 'bbox',
          'flag': 'resize',
          'groupname': groupname,
          'username': username,
          'gid': gid,
          'uid': uid,
          'id': this.id,
          'box': this
        }
        if(wsock) {
          wsock.send(JSON.stringify(data));
        }
      }
      redrawCanvas();
    }
  },


  chkRemoveClick: function(pt) {
    this.writeDebug("chkRemoveClick: this.y " + this.y + " pt.Y " + pt.Y + " this.x " + this.x + " pt.X " + pt.X, 5);
    if( pt.Y > this.y+4 && 
        pt.Y <= (this.y+this.hdrsz)-4 &&
        pt.X > ((this.x + this.w) - 20) && 
        pt.X <= ((this.x + this.w) - 4)
      ) {
      return true;
      this.remove();
      //reset the global selected flag since the global pen events will call the specific box pen evens
      selected = -1;
    }
  },


  chkAnchorClick: function(pt) {
    this.writeDebug("chkAnchorClick: pt.X=" + pt.X + " pt.Y=" + pt.Y + 
                    " this.x=" + this.x + " this.y=" + this.y, 6);
    //north-west
    //cctx.fillRect(this.x, this.y, this.anchorsz, this.anchorsz);
    if(pt.X > this.x && pt.X <= this.x+this.anchorsz &&
        pt.Y > this.y && pt.Y <= this.y+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: NW selected", 9);
      return "NW";
    }
    //north
    //cctx.fillRect(this.x+(this.w/2), this.y, this.anchorsz, this.anchorsz);
    if(pt.X > (this.x+(this.w/2)) && pt.X <= (this.x+(this.w/2))+this.anchorsz &&
        pt.Y > this.y && pt.Y <= this.y+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: N selected", 9);
      return "N";
    }
    //north-east
    //cctx.fillRect(this.x+this.w-this.anchorsz, this.y, this.anchorsz, this.anchorsz);
    if(pt.X > this.x+this.w-this.anchorsz && pt.X <= (this.x+this.w-this.anchorsz)+this.anchorsz &&
        pt.Y > this.y && pt.Y <= this.y+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: NE selected", 9);
      return "NE";
    }
    //west
    //cctx.fillRect(this.x, this.y+(this.h/2), this.anchorsz, this.anchorsz);
    if(pt.X > this.x && pt.X <= this.x+this.anchorsz &&
        pt.Y > this.y+(this.h/2) && pt.Y <= (this.y+(this.h/2))+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: W selected", 9);
      return "W";
    }

    //south-west
    //cctx.fillRect(this.x, this.y+this.h-this.anchorsz, this.anchorsz, this.anchorsz);
    if(pt.X > this.x && pt.X <= this.x+this.anchorsz &&
        pt.Y > this.y+this.h-this.anchorsz && pt.Y <= (this.y+this.h-this.anchorsz)+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: SW selected", 9);
      return "SW";
    }

    //south
    //cctx.fillRect(this.x+(this.w/2), this.y+this.h-this.anchorsz, this.anchorsz, this.anchorsz);
    if(pt.X > this.x+(this.w/2) && 
        pt.X <= this.x+(this.w/2)+this.anchorsz &&
        pt.Y > this.y+this.h-this.anchorsz && 
        pt.Y <= (this.y+this.h-this.anchorsz)+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: S selected", 9);
      return "S";
    }
    //south-east
    //cctx.fillRect(this.x+this.w-this.anchorsz, this.y+this.h-this.anchorsz, this.anchorsz, this.anchorsz);
    if(pt.X > this.x+this.w-this.anchorsz && 
        pt.X <= (this.x+this.w-this.anchorsz)+this.anchorsz &&
        pt.Y > this.y+this.h-this.anchorsz && 
        pt.Y <= (this.y+this.h-this.anchorsz)+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: SE selected", 9);
      return "SE";
    }
    //east
    //cctx.fillRect(this.x+this.w-this.anchorsz, this.y+(this.h/2), this.anchorsz, this.anchorsz);
    if(pt.X > this.x+this.w-this.anchorsz && 
        pt.X <= (this.x+this.w-this.anchorsz)+this.anchorsz &&
        pt.Y > this.y+(this.h/2) && 
        pt.Y <= (this.y+(this.h/2))+this.anchorsz ) {
      this.writeDebug("chkAnchorClick: E selected", 9);
      return "E";
    }
    return "";
  },


  writeDebug: function(msg, level) {
    writeDebug("BBox id " + this.id + ": " + msg, level);
  }
}
